package checker

import (
	"bytes"
	"mi/compiler/ast"
	"mi/compiler/binder"
	"mi/compiler/token"
)

func (c *checker) getSymbolAccessChain(symbol *binder.Symbol, useAlias bool) string {
	var getSymbolName = func(symbol *binder.Symbol) string {
		var realSymbol = symbol
		if symbol.Flags&binder.AliasLike != 0{
			realSymbol = c.getSymbolLinks(realSymbol).mappedSymbol
		}

		var result = realSymbol.Name
		if useAlias {
			var declaration = realSymbol.ValueDeclaration
			if !isNull(declaration) {
				var aliasTag = binder.GetNodeAliasDeclaration(declaration)
				if !isNull(aliasTag) && !isNull(aliasTag.Name) {
					return aliasTag.Name.Value
				}
			}
		}

		return result
	}

	var symbolName string
	var currentSymbol = symbol
	for currentSymbol != nil {
		var currentSymbolName = getSymbolName(currentSymbol)
		var isFirstName = len(symbolName) == 0
		if isFirstName {
			symbolName = currentSymbolName
		} else {
			symbolName = currentSymbolName + "." + symbolName
		}

		currentSymbol = currentSymbol.Parent
	}

	return symbolName
}

// Enclosing declaration is optional when we don't want to get qualified name in the enclosing declaration scope
// Meaning needs to be specified if the enclosing declaration is given
func (c *checker) symbolToString(symbol *binder.Symbol, enclosingDeclaration ast.Node, meaning binder.SymbolFlags, useAlias bool) string {

	var getSymbolName = func(symbol *binder.Symbol) string {
		var realSymbol = symbol
		if symbol.Flags&binder.AliasLike != 0{
			realSymbol = c.getSymbolLinks(realSymbol).mappedSymbol
		}

		var result = realSymbol.Name
		if useAlias {
			var declaration = realSymbol.ValueDeclaration
			if !isNull(declaration) {
				var aliasTag = binder.GetNodeAliasDeclaration(declaration)
				if !isNull(aliasTag) && !isNull(aliasTag.Name)  {
					return aliasTag.Name.Value
				}
			}
		}

		return result
	}

	// Get qualified name
	if enclosingDeclaration != nil &&
		// Type parameters do not need qualification
		(symbol.Flags&binder.TypeParameter) == 0 {
		var symbolName string
		for symbol != nil {
			var isFirstName = len(symbolName) == 0

			var currentSymbolName = getSymbolName(symbol)
			if isFirstName {
				symbolName = currentSymbolName
			} else {
				symbolName = currentSymbolName + "." + symbolName
			}

			var resolvedSymbol = c.resolveNameNoReportError(enclosingDeclaration, symbol.Name, symbol.Flags)
			if resolvedSymbol != nil {
				break
			}

			symbol = c.GetParentOfSymbol(symbol)
			if symbol.Flags&binder.Package != 0 {
				var pkg = symbol.Declarations[0].(*ast.Package)
				if path := c.program.GetPackagePath(pkg); len(path) > 0 {
					symbolName = path + "." + symbolName
					break
				}
			}

			meaning = c.getQualifiedLeftMeaning(meaning)
		}

		return symbolName
	}

	return getSymbolName(symbol)
}

func (c *checker) getQualifiedLeftMeaning(rightMeaning binder.SymbolFlags) binder.SymbolFlags {
	// If we are looking in value space, the parent meaning is value, other wise it is namespace
	if rightMeaning == binder.Value {
		return binder.Value
	} else {
		return binder.Namespace
	}
}

func (c *checker) typeToString(tpe Type, enclosingDeclaration ast.Node, useAlias bool) string {
	// TODO(shkamat): TypeToString should take enclosingDeclaration as input, once we have implemented enclosingDeclaration
	var typeWriter = typeWriter{
		checker:              c,
		useAlias:             useAlias,
		enclosingDeclaration: enclosingDeclaration,
	}
	return typeWriter.writeType(tpe)
}

type typeWriter struct {
	checker              *checker
	useAlias             bool
	writer               bytes.Buffer
	enclosingDeclaration ast.Node
}

func (t *typeWriter) writeType(tpe Type) string {
	if tpe.Flags()&TFIntrinsic != 0 {
		if t.useAlias {
			t.writer.WriteString(tpe.(*intrinsicType).name)
		} else {
			t.writer.WriteString(tpe.(*intrinsicType).realName)
		}

	} else if tpe.Flags()&TFReference != 0 {
		t.writeTypeReference(tpe.(TypeReference))
	} else if tpe.Flags()&(TFClass|TFInterface|TFAnnotation|TFEnum|TFTypeParameter) != 0 {
		t.writer.WriteString(t.checker.symbolToString(tpe.Symbol(), t.enclosingDeclaration, binder.Type, t.useAlias))
	} else if tpe.Flags()&TFAnonymous != 0 {
		t.writeAnonymousType(tpe.(ObjectType))
	} else {
		// Should never get here
		t.writer.WriteString("(未知)")
	}

	return t.writer.String()
}

func (t *typeWriter) writeTypeReference(reference TypeReference) {
	if reference.Target() == t.checker.globalArrayType {
		// If we are writing array element type the arrow style signatures are not allowed as
		// we need to surround it by curlies, e.g. { (): T; }[]; as () => T[] would mean something different
		t.writeType(reference.TypeArguments()[0])
		t.writer.WriteString("[]")
	} else {
		t.writer.WriteString(t.checker.symbolToString(reference.Target().Symbol(),
			t.enclosingDeclaration, binder.Type, t.useAlias))
		t.writer.WriteString("<")
		for i := 0; i < len(reference.TypeArguments()); i++ {
			if i > 0 {
				t.writer.WriteString(", ")
			}
			t.writeType(reference.TypeArguments()[i])
		}
		t.writer.WriteString(">")
	}
}

func (t *typeWriter) writeAnonymousType(tpe ObjectType) {
	// Always use 'typeof T' for type of class, enum, and module objects
	if tpe.Symbol() != nil && tpe.Symbol().Flags&(binder.Class|binder.Enum) != 0 {
		t.writeTypeofSymbol(tpe)
	} else {
		t.writer.WriteString("(匿名)")
	}
}

func (t *typeWriter) writeTypeofSymbol(tpe ObjectType) {
	t.writer.WriteString("typeof ")
	t.writer.WriteString(t.checker.symbolToString(tpe.Symbol(),
		t.enclosingDeclaration, binder.Value, t.useAlias))
}

func (t *typeWriter) writeSignature(signature *Signature, arrowStyle bool) {
	if signature.TypeParameters != nil {
		t.writer.WriteString("<")
		for i := 0; i < len(signature.TypeParameters); i++ {
			if i > 0 {
				t.writer.WriteString(", ")
			}
			var parameter = signature.TypeParameters[i]
			t.writer.WriteString(t.checker.symbolToString(parameter.Symbol(),
				nil, 0, t.useAlias))
			var constraint = t.checker.getConstraintOfTypeParameter(parameter)
			if constraint != nil {
				t.writer.WriteString(" ")
				t.writer.WriteString(token.ExtendsKeyword.ToString())
				t.writer.WriteString(" ")
				t.writeType(constraint)
			}
		}
		t.writer.WriteString(">")
	}
	t.writer.WriteString("(")
	for i := 0; i < len(signature.Parameters); i++ {
		if i > 0 {
			t.writer.WriteString(", ")
		}

		var p = signature.Parameters[i]
		var declaration = p.ValueDeclaration.(*ast.ParameterDeclaration)
		if ast.IsRestParameter(declaration) {
			t.writer.WriteString("...")
		}
		t.writer.WriteString(t.checker.symbolToString(p,
			nil, 0, t.useAlias))
		t.writer.WriteString(" ")
		t.writeType(t.checker.getTypeOfSymbol(p))
	}
	if arrowStyle {
		t.writer.WriteString(") -> ")
	} else {
		t.writer.WriteString( ") ")
	}
	t.writeType(t.checker.getReturnTypeOfSignature(signature))
}

func (c *checker) signatureToString(name string, signature *Signature, enclosingDeclaration ast.Node, useAlias bool) string {
	var writer bytes.Buffer

	writer.WriteString(name)
	if len(signature.TypeParameters) > 0 {
		writer.WriteString("<")
		for i, parameter := range signature.TypeParameters {
			if i > 0 {
				writer.WriteString(", ")
			}
			writer.WriteString(c.typeToString(parameter, enclosingDeclaration, useAlias))
		}
		writer.WriteString(">")
	}
	writer.WriteString("(")
	for i, parameter := range signature.Parameters {
		if i > 0 {
			writer.WriteString(", ")
		}

		var declaration = parameter.ValueDeclaration.(*ast.ParameterDeclaration)
		if !isNull(declaration.Dots) {
			writer.WriteString("...")
			writer.WriteString(" ")
		}
		writer.WriteString(parameter.Name)
		writer.WriteString(": ")
		var tpe = c.getTypeOfSymbol(parameter)
		writer.WriteString(c.typeToString(tpe, enclosingDeclaration, useAlias))
	}
	writer.WriteString(")")

	if !isNull(signature.ResolvedReturnType) {
		writer.WriteString(": ")
		writer.WriteString(c.typeToString(signature.ResolvedReturnType, enclosingDeclaration, useAlias))
	}

	//if len(signature.Throws) > 0 {
	//	writer.WriteString(" ")
	//	writer.WriteString(token.ThrowsKeyword.ToString())
	//	writer.WriteString(" ")
	//	for _, throw := range signature.Throws {
	//		writer.WriteString(c.typeToString(throw, enclosingDeclaration, useAlias))
	//	}
	//}

	return writer.String()
}
